Durante esta ejercitación, utilizaremos los materiales de la unidad Nº 1, así como la teoría correspondiente.
El corpus que les presento está relacionado con los módulos
anteriores de la Diplomatura y se encuentra disponible en el campus.
Este corpus está compuesto por textos escritos por
viajeros, cronistas, naturistas y antropólogos que realizaron viajes por
la región actual de Argentina y algunos países limítrofes, entre los
siglos XVI y XVIII. Los autores incluidos son: Félix de Azará, Ruy Díaz
de Guzmán, Acarette du Biscay, William H. Hudson, Pero Hernández,
Francisco Pascasio Moreno, Ulrico Schmidl, Antonio Pigafetta y Antonio
de Viedma.
Cada texto ha sido extraído de diferentes repositorios, convertido a
formato de texto plano .txt y limpiado para su utilización
en esta unidad y en la siguiente. En la carpeta comprimida encontrarán
cada uno de los textos nombrados con el apellido del autor, seguido de
un guion bajo _ y una sugerencia del nombre del documento,
con la extensión ‘.txt’.
rm(list = ls()) #limpiamos el entorno
getwd() #nos ubicamos en nuestra carpeta
Primero paso: Puedes descargar y descomprimir como
lo realizas habitualmente la carpeta directamente del campus, o sino
mediante el comando download.file descargarás el archivo
*.zip con la url entre comillas, luego indicarás el nombre del archivo y
finalmente con el parámetro wget se indica el método
utilizado para realizar la descarga, aclaración en
Windows debes cambiarlo por wininet. Luego, descomprime
el archivo con el comando unzip. A continuación ingresa a
tu carpeta de trabajo.
#download.file("https://github.com/rominicky/materiales/raw/refs/heads/main/assets/corpus.zip", destfile="corpus.zip", "wget") # Aclaración para Windows reemplazar 'wget' por "wininet"
#unzip("corpus.zip") #descomprimir la carpeta
setwd('./corpus') # Ingresa a la carpeta que se ha creado, recuerda que puedes incluso crear otra carpeta para datos de entrada (input) y datos de salida (output) para cargar y guardar tus archivos
El paso a seguir es llamar a las librerías que utilizaremos:
tidyverse y tidytext fueron explicados en
la Unidad Nº 11.ggplot2 es un paquete que permite la visualización de
datos, implementa una representación organizada y en capas, de marcos,
ejes, textos, títulos, etc., utilizando diferentes colores, símbolos,
tamaños, etc. Fue desarrollada por L. Wilkinson et. al en 20002. No hace
falta llamarlo pues se encuentra incluido dentro de
tidyverse, sin embargo, es fundamental considerarlo
porque es muy útil en R. Asimismo, en algunas ocasiones si al
graficar se encuentra un error, prueba de cargar nuevamente la librería.
Ten en cuenta este dato para la mayoría de las librerías.library("tidytext", "tidyverse")
Una vez instalados estos requerimientos, llamarás a la función
list.file para corroborar que los archivos necesarios se
encuentran en la carpeta donde estás trabajando.
list.files('./corpus')
En un segundo paso, conformarás una nueva lista, a
la que puedes llamar archivos, cuyos elementos serán los
textos planos dentro de la carpeta corpus, para ello,
utilizarás, nuevamente, la función list.files, y le
indicarás que tipo de archivo debe contener, txt, con
la declaración pattern. Recuerda estar trabajando en la
carpeta donde se encuentran tus archivos, sino deberás incorporar un
declaración que indique la ubicación, path =, por ejemplo:
list.files(path = "corpus", pattern = "\\.txt$").
archivos <- list.files(path = "./corpus",pattern = "\\.txt$")
archivos #corroborá que la lista está correcta
## [1] "Azara_Descripcion.txt" "DiazDeGuzman_ArgManus.txt"
## [3] "DuBiscay_RelDeUnViaje.txt" "Hudson_DiasDeOcio.txt"
## [5] "PHernandez_RelCosas.txt" "Pigafetta_PrimerViaje.txt"
## [7] "PMoreno_ViajePatagonia.txt" "Schmidl_ViajeAlRdP.txt"
## [9] "Viedma_Diario.txt"
class(archivos) #la función 'class()' devuelve el tipo de objeto, en este caso podrás ver que se trata de una lista de caracteres
## [1] "character"
length(archivos) #la función 'length()' permite obtener el tamaño del objeto que se incluye dentro de los paréntesis
## [1] 9
Tercer paso, será para facilitar la visualización de cada texto en
los siguientes pasos, por lo cual, se eliminará lo que no es necesario
del nombre de cada archivo. Para ello, utilizarás la función
gsub, de gran utilidad para limpieza de datos que está
incluida por defecto en R. Los parámetros indicarán,
según orden de escritura, caracteres que se eliminarán (\\. será para
indicar que se busca un . y no otro
carácter según las reglas de expresión regular), los que se agregarán
(las doble comillas indican que no se agregará nada), y finalmente, con
perl, se determina que se utilicen las reglas de
expresiones regulares3.
textos_archivo <- gsub("\\.txt", "", archivos, perl = TRUE)
#Ahora llama a la lista para corroborar que se ha realizado la limpieza
textos_archivo
## [1] "Azara_Descripcion" "DiazDeGuzman_ArgManus" "DuBiscay_RelDeUnViaje"
## [4] "Hudson_DiasDeOcio" "PHernandez_RelCosas" "Pigafetta_PrimerViaje"
## [7] "PMoreno_ViajePatagonia" "Schmidl_ViajeAlRdP" "Viedma_Diario"
Cuarto paso, con la lista de textos acorde para continuar el
ejercicio, procede a armar un tibble, este objeto es muy
similar a los data.frame presentados en la unidad anterior, pues es
rectangular, organizado en filas y columnas, y pertenece al paquete
tidyverse. Existen tres diferencias entre los
data.frame y los tibble, la primera es
como se muestran en consola; segunda, los tibble eliminan por defecto
rownames, e incluso no se recomienda su uso, para
evitar problemas de compatibilidad principalmente con bases de datos
SQL; por último, no convierte a las string (cadenas de
caracteres) en factores. Igualmente, ambos objetos son intercambiables4.
Armarás el tibble con tres columnas, la primera y última tendrán
caracteres dentro de sus variables, y parrafo será
numérica, como se muestra a continuación:
library(dplyr)
##
## Adjuntando el paquete: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
mensajes <- tibble(textos_archivo = character(),
parrafo = numeric(),
texto = character())
#Si llamas a "mensajes" comprobarás que figura como vacío, esto es porque aún no se ha completado con datos, ello lo realizarás en el próximo paso.
mensajes
Para poder completar el tibble mensajes que has
creado, realizarás una iteración con el bucle for, que
permite repetir instrucciones, evaluando el mismo código para cada
elemento de un vector o lista.
En este ejercicio, se le indica que comience la iteración en 1 hasta
el tamaño (length) de archivos. Luego,
armará en el objeto corpus lo que leerá con la función
read_lines, allí se indicará que con la función
paste junte los textos dentro de tu carpeta de trabajo,
importante añadir esa dirección, con el
contenido de archivos[i] , cuyo valor cambiará en cada
repetición del bucle. El parámetro sep = será para indicar
que los pegue con “/“ .
Paso siguiente, convertir lo que acaba de leer en una nueva tabla,
otra tibble llamada temporal, tal como hiciste con la
anterior de mensajes, indicando que complete con cada
texto. La primer columna, parrafo indicará el número de
párrafos de cada texto, se lo adjudicará con la función
seq_along que contará hasta el número máximo en el objeto
corpus_total y por último, en texto se
agregará cada párrafo. Para finalizar la repetición, indicarás que en
mensajes se sume la tabla temporal,
mediante la función bind_rows.
library(readr)
#Iteración
for (i in 1:length(archivos)){
corpus_total <- read_lines(paste('./corpus',
archivos[i],
sep = "/"))
temporal <- tibble(textos_archivo = textos_archivo[i],
parrafo = seq_along(corpus_total),
texto = corpus_total)
mensajes <- bind_rows(mensajes, temporal)
}
mensajes #corrobora que se creó correctamente la tabla
Este sexto paso, será para proceder con la tokenización del corpus,
es decir extraer sus unidades de análisis. Para ello, se utilizará la
función unnest_tokens que se encuentra dentro del paquete
tidytext; se indicará que a cada fila de
parrafo, lo separe a nivel de palabra según la columna
texto.
mensajes_palabras estará formado por tres columnas, una que indicará el texto, la otra el número de párrafo y finalmente, las palabras de cada uno, pero separadas. Recuerda que el operador pipe (%>%) fue presentado en la primera unidad.
mensajes_palabras <- mensajes %>%
unnest_tokens(palabra, texto)
mensajes_palabras
Como siguiente paso, se eliminarán las palabras vacías, que no serán útil en este ejercicio, pero si pueden servir para otros análisis; asimismo, lo realizarás para continuar con la parte de ejercitación que solicitaré como extra. Para ello, se armará una una tabla con las palabras que se obtienen de la función get_stopwords que forma parte del paquete tidytext, y se aclarará que se utilice en español.
stopwords1 <- get_stopwords("es")
stopwords1
Paso siguiente, se le indicará que cambie la columna
word por palabra, mediante la función
rename, que forma parte del paquete
dplyr5, que a su vez pertenece a
tidyverse, es útil para cambiar el nombre de variables
u objetos. Cuestión que debe considerarse al momento de renombrar, es
que primero debe escribirse el nuevo término, y a continuación el que se
va a cambiar. Este paso es importante para poder eliminar las palabras
vacías de mensajes_palabras.
stopwords1 <- stopwords1 %>%
rename(palabra = word)
stopwords1
Ahora, para poder obtener una nueva lista con palabras vacías
excluidas, se usará la función anti_join que compara las
columnas con el mismo nombre, en este caso palabra y
elimina los elementos que coinciden. Cuando finalice la ejecución,
aparece un mensaje que significa que las dos listas tienen iguales
elementos en esas columnas, en caso contraria, devolverá mensaje de
error.
mensajes_sin_vacias <- mensajes_palabras %>%
anti_join(stopwords1)
## Joining with `by = join_by(palabra)`
Como paso siguiente, se procederá a la limpieza de la lista mensajes_palabras y mensajes_sin_vacias. Para ello, se eliminarán dígitos, palabras con menos de tres caracteres, celdas sin caracteres y las que contengan puntos, todo ello se realizará en la columna palabra. Por lo cual, se utilizarán las siguientes funciones:
mutate, que crea, modifica y elimina columnas según ciertos parámetros que se le adjudican, se encuentra dentro del paquete tidyverse. En este caso se le sumará str_remove_all (función que se utiliza para eliminar coincidencias en una cadena de caracteres) para que elimine en la columna palabra los números que aparezcan.
filter, será para filtrar las palabras con menos de tres caracteres, con la ayuda de nchar que cuenta el número de caracteres; también se utilizará para eliminar los espacios con valores de caracteres vacíos y cuando encuentre un . o n° (símbolo de número que figura en los textos).
Repetirás las limpieza en las dos listas, recuerda guardar las que no fueron limpias, adjudicando nuevos nombres a las que si se hayan limpiado.
library(stringr)
mensajes_pal_limpio <- mensajes_palabras %>%
mutate(palabra = str_remove_all(palabra, "\\d+")) %>%
filter(nchar(palabra) > 3) %>%
filter(palabra != "") %>%
filter(palabra != ".") %>%
filter(palabra != "n°")
mensajes_pal_limpio
Indica nuevo nombre a esta tabla que se ha limpiado. Esta nueva tabla será útil para realizar la parte de ejercitación extra.
mensajes_limpio <- mensajes_sin_vacias %>%
mutate(palabra = str_remove_all(palabra, "\\d+")) %>%
filter(nchar(palabra) > 3) %>%
filter(palabra != "") %>%
filter(palabra != ".") %>%
filter(palabra != "n°")
mensajes_limpio
En el paso que sigue, se dará inicio a los análisis de frecuencia y gráficos de los textos del corpus. Para lo cual, primero se contará las palabras que se encuentran dentro de la columna palabra en mensajes_pal_limpio, por medio de la función count, que también pertenece al paquete tidyverse, y presentará la frecuencia absoluta de cada una, o sea la cantidad de veces que se repite.
mensajes_pal_limpio %>%
count(palabra, sort = TRUE)
Como la frecuencia absoluta no representa ningún dato novedoso, se calculará el promedio de palabras por texto, que no han sido procesadas con la limpieza de datos, pudiéndose calcular entre el resultado del número de columnas obtenidos con la función nrow y el tamaño de la lista archivos, calculado en los primeros pasos.
nrow(mensajes_palabras) / length(archivos)
## [1] 49276.67
Ese dato solo indicará un promedio, por lo cual, a continuación se calculará la frecuencia relativa por palabras y será adjudica como una nueva columna, dentro del tibble limpio: mensajes_pal_limpio, que se denominará relativa. Por lo cual tendremos cuatro columnas, la del término, su frecuencia absoluta, la relativa y el TF-IDF. Por ello armaremos un nuevo tibble denominado, mensajes_frecuencias:
# Calculo de la frecuencia absoluta, relativa y TF-IDF
mensajes_frec_limpio <- mensajes_pal_limpio %>%
count(palabra, sort = TRUE) %>%
mutate(
relativa = n / sum(n), # Frecuencia relativa
tf_idf = relativa * log(nrow(mensajes_pal_limpio) / n) # Cálculo TF-IDF
)
# Mostrar resultado con la nueva columna de TF-IDF
mensajes_frec_limpio
También, podrías pedir que devuelva la frecuencia por cada texto, para ello, se deberá agrupar por cada uno mediante la función group_by, del paquete tidyverse, únicamente indicando que debe considerar la lista de textos, antes preparada; luego del cálculo de la frecuencia absoluta, debe crearse una nueva columna para la frecuencia relativa, y finalmente que desagrupe los datos, con ungroup(). Todo ello, será guardado en otra tabla frecuencia_text_arch:
frecuencia_text_arch <- mensajes_pal_limpio %>%
group_by(textos_archivo) %>%
count(palabra, sort = T) %>%
mutate(
relativa = n / sum(n), # Frecuencia relativa
tf_idf = relativa * log(nrow(mensajes_pal_limpio) / n) # Cálculo TF-IDF
) %>%
ungroup()
frecuencia_text_arch
Ahora, para visualizar estos datos calculados realizarás un gráfico, donde se presentará la frecuencia por cada texto, con la función ggplot, que fue convocada al principio de la práctica con el paquete tidyverse, se le indicará que se graficará en barras, mediante el parámetro geom_bar, luego se indicará con aes, el contenido de los eje X e Y, en ese orden; y por último, con stat, que tipo de transformación estadísticas tendrán los datos.
library(ggplot2)
mensajes_pal_limpio %>%
group_by(textos_archivo) %>%
count(palabra, sort = T) %>%
ggplot() +
geom_bar(aes(textos_archivo,
n, fill = palabra), #la variación de color es por palabras si cambiamos palabras, por textos_archivos, será por cada documento
stat = 'identity') +
theme(legend.position = "none")
#ggsave("mensajes_pal_limpio.png")
Otro gráfico que se puede realizar es observando las palabras más frecuentes de los textos, para ello primero, se llamará a mensajes_pal_limpio, se pedirá que cuente las ocurrencias, ordenadas de mayor a menor; en la tercera línea se indicará que filtre las ocurrencias mayores a 500, pues se han eliminado las palabras vacías. Como se explicó antes, al crear una nueva tabla interna se debe indicar con mutate, la nueva columna, que almacenará los nuevos datos. Luego se indicarán los parámetros del gráfico. Aclaración, ggplot une sus parámetros con +; el argumento fill hará que cada palabra tenga un color determinado, stat indicará que las barras tendrán la altura según n. Las líneas siguientes presentarán parámetros para título, ubicación de leyenda y etiquetas en los ejes; y con coord_flip() se asegura que el eje Y se imprima de forma horizontal.
mensajes_pal_limpio %>%
count(palabra, sort = T) %>%
filter(n > 500) %>%
mutate(palabra = reorder(palabra, n)) %>%
ggplot(aes(x = palabra, y = n, fill = palabra)) +
geom_bar(stat="identity") +
theme_minimal() +
theme(legend.position = "none") +
ylab("Número de veces que aparecen") +
xlab(NULL) +
ggtitle("Discurso-DNU") +
coord_flip()
ggsave("mensajes_palabras.png")
## Saving 7 x 5 in image
Otra utilidad, que ha sido explicada en la parte teórica de la
Unidad 2, es la Ley de Zipf, por lo cual, se
reorganizarán los parámetros de ggplot para poder apreciar
la relación entre cada palabra y su frecuencia.
Para ello, primero se armará una tabla con mensajes_palabras, con columnas con frecuencia absoluta = n y frecuencia relativa, sumada una última columna, que indicará el orden de palabras en forma descendente, o el número de filas, llamada Clasificacion.
mensajes_frecuencias <- mensajes_palabras %>%
count (palabra, sort = TRUE) %>%
mutate( relativa = n / sum(n), # Frecuencia relativa
tf_idf = relativa * log(nrow(mensajes_pal_limpio) / n) # Cálculo TF-IDF
) %>%
mutate(Clasificacion = row_number())
mensajes_frecuencias
Para realizar el gráfico con dichos datos, se utilizará la tabla anterior, y se indicará en la tercera línea, mediante geom_line la forma en que va a realizar la línea del gráfico; y en las últimas dos, se indicará que en ambos ejes se usará la escala logarítimica.
mensajes_frecuencias %>%
ggplot(aes(Clasificacion, relativa, color = n)) +
geom_line(size = 1.5, alpha = 0.8, show.legend = FALSE) + # Línea más delgada y con transparencia ajustada
scale_x_log10() + # Escala logarítmica en el eje X
scale_y_log10() + # Escala logarítmica en el eje Y
scale_color_gradient(low = "lightblue", high = "lightpink") + # Gradiente de color
labs(
title = "Comprobación Ley de Zipf",
x = "Clasificación",
y = "Frecuencia Relativa"
) +
theme_minimal()
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
Recordemos también otra ley que hemos trabajado en la parte teórica,
la ley de Heaps, para lo cual primero calcularemos el
total_palabras, es decir, el número total de palabras
procesadas hasta ese punto en el texto y el vocabulario,
que representará el conteo acumulativo de palabras únicas encontradas en
el texto. Esto nos permitirá analizar cómo crece el vocabulario a medida
que se procesan más palabras en cada texto, posteriormente
visualizaremos los datos mediante ggplot2.
# Calcular el crecimiento del vocabulario
crecimiento_vocabulario <- mensajes_palabras %>%
# Excluir el texto "Azara_Descripcion"
# filter(!textos_archivo %in% c("Azara_Descripcion", "PMoreno_ViajePatagonia")) %>%
# Contar el total de palabras procesadas
group_by(textos_archivo) %>%
mutate(total_palabras = row_number()) %>%
# Contar el vocabulario único a medida que se procesan las palabras
mutate(vocabulario = cumsum(!duplicated(palabra))) %>%
ungroup() # Desagrupar para evitar problemas con ggplot
Ahora, generaremos una gráfica que muestre cómo crece el vocabulario (número de palabras únicas) en función del número total de palabras procesadas para cada uno de los textos que hemos analizado. Podremos observar líneas de diferentes colores que representan los diferentes textos, y puntos, que no son muy útiles por la cantidad de datos, y deberían añadir mayor claridad a la visualización del crecimiento del vocabulario. La gráfica es una representación visual útil para observar la Ley de Heaps y cómo el vocabulario se expande a medida que se procesan más palabras.
# Graficar la Ley de Heaps
ggplot(crecimiento_vocabulario, aes(x = total_palabras, y = vocabulario, color = textos_archivo)) +
geom_line(aes(group = textos_archivo), size = 0.1) + # Línea para cada texto
geom_point(size = 1.5) + # puntos, poco útiles, porque no agregan mayor claridad, he cambiado el tamaño de puntos y de líneas, pero no varía demasiado
labs(
title = "Comprobación de la ley de Heaps",
x = "Total de palabras",
y = "Vocabulario o palabras únicas"
) +
theme_minimal() +
scale_color_discrete(name = "Textos") # Agregar leyenda para los textos
Para más información sobre tidyverse
acceder a https://www.tidyverse.org/learn/, y para más información
sobre tidytext en https://cran.r-project.org/web/packages/tidytext/tidytext.pdf.↩︎
Pueden encontrar más información sobre la librería en: https://ggplot2.tidyverse.org/.↩︎
Para más información sobre expresiones regulares, pueden acceder a: https://indexingdata.com/blog/analitica-web/guia-basica-expresiones-regulares/, https://regex101.com/ este sitio es para ejercitar nuestras regex y posee en el cuadrante inferior derecho una guía rápida, y también en https://www.rexegg.com/regex-quickstart.php.↩︎
Puedes acceder a más información sobre
tibble en https://cran.r-project.org/web/packages/tibble/vignettes/tibble.html;
y sobre data.frame en https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/data.frame.
Los tibbles son una versión mejorada de los
data.frames, pues son diseñados para facilitar la
manipulación y visualización de datos en R. Si bien se pueden utilizar
ambos, los tibbles son especialmente compatibles con
dplyr y tidyverse.↩︎
Para más información sobre la librería dplyr acceder a https://dplyr.tidyverse.org/.↩︎